﻿namespace System.ServiceModel.DomainServices.Client
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Threading;
    using System.Windows.Data;

    public sealed class EntityCollection<TEntity> : IEntityCollection, IEnumerable<TEntity>, IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged, ICollectionViewFactory where TEntity: Entity
    {
        private AssociationAttribute _assocAttribute;
        private Action<TEntity> _attachAction;
        private TEntity _attachingEntity;
        private Action<TEntity> _detachAction;
        private TEntity _detachingEntity;
        private List<TEntity> _entities;
        private bool _entitiesAdded;
        private bool _entitiesLoaded;
        private Func<TEntity, bool> _entityPredicate;
        private bool _isComposition;
        private Entity _parent;
        private EntitySet _sourceSet;
        private EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityAdded;
        private EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityRemoved;
        private EventHandler _collectionChangedEventHandler;
        private EventHandler _propertyChangedEventHandler;

        public event EventHandler<EntityCollectionChangedEventArgs<TEntity>> EntityAdded
        {
            add
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityAdded = this.entityAdded;
                do
                {
                    handler2 = entityAdded;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Combine(handler2, value);
                    entityAdded = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityAdded, handler3, handler2);
                }
                while (entityAdded != handler2);
            }
            remove
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityAdded = this.entityAdded;
                do
                {
                    handler2 = entityAdded;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Remove(handler2, value);
                    entityAdded = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityAdded, handler3, handler2);
                }
                while (entityAdded != handler2);
            }
        }

        public event EventHandler<EntityCollectionChangedEventArgs<TEntity>> EntityRemoved
        {
            add
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityRemoved = this.entityRemoved;
                do
                {
                    handler2 = entityRemoved;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Combine(handler2, value);
                    entityRemoved = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityRemoved, handler3, handler2);
                }
                while (entityRemoved != handler2);
            }
            remove
            {
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler2;
                EventHandler<EntityCollectionChangedEventArgs<TEntity>> entityRemoved = this.entityRemoved;
                do
                {
                    handler2 = entityRemoved;
                    EventHandler<EntityCollectionChangedEventArgs<TEntity>> handler3 = (EventHandler<EntityCollectionChangedEventArgs<TEntity>>) Delegate.Remove(handler2, value);
                    entityRemoved = Interlocked.CompareExchange<EventHandler<EntityCollectionChangedEventArgs<TEntity>>>(ref this.entityRemoved, handler3, handler2);
                }
                while (entityRemoved != handler2);
            }
        }

        event NotifyCollectionChangedEventHandler CollectionChanged;

        event PropertyChangedEventHandler PropertyChanged;

        public EntityCollection(Entity parent, string memberName, Func<TEntity, bool> entityPredicate)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }
            if (string.IsNullOrEmpty(memberName))
            {
                throw new ArgumentNullException("memberName");
            }
            if (entityPredicate == null)
            {
                throw new ArgumentNullException("entityPredicate");
            }
            this._parent = parent;
            this._entityPredicate = entityPredicate;
            PropertyInfo property = this._parent.GetType().GetProperty(memberName, MetaType.MemberBindingFlags);
            if (property == null)
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.Property_Does_Not_Exist, new object[] { parent.GetType(), memberName }), "memberName");
            }
            this._assocAttribute = property.GetCustomAttributes(false).OfType<AssociationAttribute>().SingleOrDefault<AssociationAttribute>();
            if (this._assocAttribute == null)
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.MemberMustBeAssociation, new object[] { memberName }), "memberName");
            }
            this._isComposition = property.GetCustomAttributes(typeof(CompositionAttribute), false).Any<object>();
            this._parent.RegisterSetChangedCallback(new Action(this.OnEntitySetChanged));
            this._parent.PropertyChanged += new PropertyChangedEventHandler(this.ParentEntityPropertyChanged);
        }

        public EntityCollection(Entity parent, string memberName, Func<TEntity, bool> entityPredicate, Action<TEntity> attachAction, Action<TEntity> detachAction) : this(parent, memberName, entityPredicate)
        {
            if (attachAction == null)
            {
                throw new ArgumentNullException("attachAction");
            }
            if (detachAction == null)
            {
                throw new ArgumentNullException("detachAction");
            }
            this._attachAction = attachAction;
            this._detachAction = detachAction;
        }

        public void Add(TEntity entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            if (this.IsSourceExternal)
            {
                throw new InvalidOperationException(Resource.EntityCollection_ModificationNotAllowedForExternalReference);
            }
            if (entity != this._attachingEntity)
            {
                if (this.SourceSet != null)
                {
                    this.SourceSet.EntityContainer.CheckCrossContainer(entity);
                }
                this.Attach(entity);
                if (!this.Entities.Contains(entity))
                {
                    bool flag = false;
                    if (this.SourceSet != null)
                    {
                        if (!this.SourceSet.IsAttached(entity))
                        {
                            entity.IsInferred = true;
                            this.SourceSet.Add(entity);
                            flag = true;
                        }
                        else if (this._isComposition && (entity.EntityState == EntityState.Deleted))
                        {
                            this.SourceSet.Add(entity);
                            flag = true;
                        }
                    }
                    if (!flag || !this.Entities.Contains(entity))
                    {
                        this.AddEntity(entity);
                        this.RaiseCollectionChangedNotification(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entity, this.Entities.Count - 1));
                    }
                    this._entitiesAdded = true;
                }
                this.Load();
                if (this._isComposition)
                {
                    entity.Parent.OnChildUpdate();
                }
            }
        }

        private void AddEntity(TEntity entity)
        {
            this.Entities.Add(entity);
            if (this._isComposition)
            {
                entity.SetParent(this._parent, this._assocAttribute);
            }
        }

        private void Attach(TEntity entity)
        {
            if (this._attachAction != null)
            {
                TEntity local = this._attachingEntity;
                this._attachingEntity = entity;
                try
                {
                    this._attachAction(entity);
                }
                finally
                {
                    this._attachingEntity = local;
                }
            }
        }

        private void Detach(TEntity entity)
        {
            if (this._detachAction != null)
            {
                TEntity local = this._detachingEntity;
                this._detachingEntity = entity;
                try
                {
                    this._detachAction(entity);
                }
                finally
                {
                    this._detachingEntity = local;
                }
            }
        }

        private bool Filter(TEntity entity)
        {
            if (entity.EntityState != EntityState.New)
            {
                return this._entityPredicate(entity);
            }
            return false;
        }

        public IEnumerator<TEntity> GetEnumerator()
        {
            this.Load();
            return this.Entities.ToList<TEntity>().GetEnumerator();
        }

        private void Load()
        {
            if ((this._parent.EntitySet != null) && !this._entitiesLoaded)
            {
                foreach (TEntity local in Enumerable.Where<TEntity>(this._parent.EntitySet.EntityContainer.GetEntitySet(typeof(TEntity)).OfType<TEntity>(), new Func<TEntity, bool>(this.Filter)))
                {
                    if (!this.Entities.Contains(local))
                    {
                        this.AddEntity(local);
                    }
                }
                this._entitiesLoaded = true;
                this.MonitorEntitySet();
            }
        }

        private void MonitorEntitySet()
        {
            if (this._parent.EntitySet != null)
            {
                if (this._entitiesAdded || this._entitiesLoaded)
                {
                    if (this._sourceSet != null)
                    {
                        this._sourceSet.CollectionChanged -= new NotifyCollectionChangedEventHandler(this.SourceSet_CollectionChanged);
                        this._sourceSet.RegisterAssociationCallback(this._assocAttribute, new Action<Entity>(this.OnEntityAssociationUpdated), false);
                    }
                    this._sourceSet = this._parent.EntitySet.EntityContainer.GetEntitySet(typeof(TEntity));
                    this._sourceSet.CollectionChanged += new NotifyCollectionChangedEventHandler(this.SourceSet_CollectionChanged);
                    this._sourceSet.RegisterAssociationCallback(this._assocAttribute, new Action<Entity>(this.OnEntityAssociationUpdated), true);
                }
            }
            else if ((this._parent.EntitySet == null) && (this._sourceSet != null))
            {
                this._sourceSet.CollectionChanged -= new NotifyCollectionChangedEventHandler(this.SourceSet_CollectionChanged);
                this._sourceSet.RegisterAssociationCallback(this._assocAttribute, new Action<Entity>(this.OnEntityAssociationUpdated), false);
                this._sourceSet = null;
            }
        }

        private void OnEntityAssociationUpdated(Entity entity)
        {
            if (((entity != this._attachingEntity) && (entity != this._detachingEntity)) && ((entity.EntityState != EntityState.New) || !entity.IsMergingState))
            {
                TEntity item = entity as TEntity;
                if ((item != null) && this._entitiesLoaded)
                {
                    bool flag = this.Entities.Contains(item);
                    if ((!flag && (this._parent.EntityState != EntityState.New)) && this.Filter(item))
                    {
                        this.AddEntity(item);
                        this.RaiseCollectionChangedNotification(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entity, this.Entities.Count - 1));
                    }
                    else if (flag && !this._entityPredicate(item))
                    {
                        int index = this.Entities.IndexOf(item);
                        this.Entities.Remove(item);
                        this.RaiseCollectionChangedNotification(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, entity, index));
                    }
                }
            }
        }

        private void OnEntitySetChanged()
        {
            if ((this._parent.EntitySet != null) && (this._sourceSet == null))
            {
                this._entitiesLoaded = false;
            }
            this.MonitorEntitySet();
        }

        private void ParentEntityPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (this._entitiesLoaded && this._assocAttribute.ThisKeyMembers.Contains<string>(e.PropertyName))
            {
                this.ResetLoadedEntities();
            }
        }

        private void RaiseCollectionChangedNotification(NotifyCollectionChangedEventArgs args)
        {
            if (args.Action == NotifyCollectionChangedAction.Add)
            {
                if (this.entityAdded != null)
                {
                    foreach (TEntity local in args.NewItems.OfType<TEntity>())
                    {
                        this.entityAdded(this, new EntityCollectionChangedEventArgs<TEntity>(local));
                    }
                }
            }
            else if ((args.Action == NotifyCollectionChangedAction.Remove) && (this.entityRemoved != null))
            {
                foreach (TEntity local2 in args.OldItems.OfType<TEntity>())
                {
                    this.entityRemoved(this, new EntityCollectionChangedEventArgs<TEntity>(local2));
                }
            }
            if (this._collectionChangedEventHandler != null)
            {
                this._collectionChangedEventHandler(this, args);
            }
            if (this._propertyChangedEventHandler != null)
            {
                this._propertyChangedEventHandler(this, new PropertyChangedEventArgs("Count"));
            }
        }

        public void Remove(TEntity entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            if (entity != this._detachingEntity)
            {
                int index = this.Entities.IndexOf(entity);
                if ((index == -1) && !this._entityPredicate(entity))
                {
                    throw new InvalidOperationException(Resource.Entity_Not_In_Collection);
                }
                if (this.IsSourceExternal)
                {
                    throw new InvalidOperationException(Resource.EntityCollection_ModificationNotAllowedForExternalReference);
                }
                this.Detach(entity);
                if ((index != -1) && this.Entities.Remove(entity))
                {
                    this.RaiseCollectionChangedNotification(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, entity, index));
                }
                if (this._isComposition)
                {
                    if ((this._sourceSet != null) && this._sourceSet.IsAttached(entity))
                    {
                        this._sourceSet.Remove(entity);
                    }
                    entity.Parent.OnChildUpdate();
                }
            }
        }

        private void ResetLoadedEntities()
        {
            Func<TEntity, bool> func = null;
            IEnumerable<TEntity> entities = this.Entities;
            this._entities = (from p in this.Entities
                where p.EntityState == EntityState.New
                select p).ToList<TEntity>();
            this._entitiesLoaded = false;
            if (this.entityRemoved != null)
            {
                if (func == null)
                {
                    func = p => !this._entities.Contains(p);
                }
                foreach (TEntity local in Enumerable.Where<TEntity>(entities, func))
                {
                    this.entityRemoved(this, new EntityCollectionChangedEventArgs<TEntity>(local));
                }
            }
            this.RaiseCollectionChangedNotification(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        private void SourceSet_CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
        {
            Func<TEntity, bool> func = null;
            if ((this._parent.EntityState != EntityState.New) && (args.Action == NotifyCollectionChangedAction.Add))
            {
                TEntity[] localArray = Enumerable.Where<TEntity>(args.NewItems.OfType<TEntity>(), new Func<TEntity, bool>(this.Filter)).ToArray<TEntity>();
                if (localArray.Length > 0)
                {
                    int index = -1;
                    List<object> source = new List<object>();
                    foreach (TEntity local in localArray)
                    {
                        index = this.Entities.Count;
                        if (!this.Entities.Contains(local))
                        {
                            this.AddEntity(local);
                            source.Add(local);
                        }
                    }
                    if (source.Count > 0)
                    {
                        this.RaiseCollectionChangedNotification(new NotifyCollectionChangedEventArgs(args.Action, source.Single<object>(), index));
                    }
                }
            }
            else if (args.Action == NotifyCollectionChangedAction.Remove)
            {
                if (func == null)
                {
                    func = p => this.Entities.Contains(p);
                }
                TEntity[] localArray2 = Enumerable.Where<TEntity>(args.OldItems.OfType<TEntity>(), func).ToArray<TEntity>();
                if (localArray2.Length > 0)
                {
                    int num2 = this.Entities.IndexOf(localArray2[0]);
                    foreach (TEntity local2 in localArray2)
                    {
                        this.Entities.Remove(local2);
                    }
                    this.RaiseCollectionChangedNotification(new NotifyCollectionChangedEventArgs(args.Action, localArray2.Single<TEntity>(), num2));
                }
            }
            else if ((args.Action == NotifyCollectionChangedAction.Reset) && this._entitiesLoaded)
            {
                this.ResetLoadedEntities();
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        ICollectionView ICollectionViewFactory.CreateView()
        {
            CollectionViewSource source = new CollectionViewSource {
                Source = new ListCollectionViewProxy<TEntity, TEntity>((EntityCollection<TEntity>) this)
            };
            return source.View;
        }

        void IEntityCollection.Add(Entity entity)
        {
            this.Add((TEntity) entity);
        }

        void IEntityCollection.Remove(Entity entity)
        {
            this.Remove((TEntity) entity);
        }

        public override string ToString()
        {
            return typeof(TEntity).Name;
        }

        public int Count
        {
            get
            {
                this.Load();
                return this.Entities.Count;
            }
        }

        private List<TEntity> Entities
        {
            get
            {
                if (this._entities == null)
                {
                    this._entities = new List<TEntity>();
                }
                return this._entities;
            }
        }

        private bool IsSourceExternal
        {
            get
            {
                return ((this.SourceSet != null) && (this.SourceSet.EntityContainer != this._parent.EntitySet.EntityContainer));
            }
        }

        private EntitySet SourceSet
        {
            get
            {
                if (this._parent.EntitySet != null)
                {
                    this._sourceSet = this._parent.EntitySet.EntityContainer.GetEntitySet(typeof(TEntity));
                }
                return this._sourceSet;
            }
        }

        AssociationAttribute IEntityCollection.Association
        {
            get
            {
                return this._assocAttribute;
            }
        }

        IEnumerable<Entity> IEntityCollection.Entities
        {
            get
            {
                return this.Cast<Entity>();
            }
        }

        bool IEntityCollection.HasValues
        {
            get
            {
                return ((this._entities != null) && (this._entities.Count > 0));
            }
        }

        private class ListCollectionViewProxy<T> : IList, ICollection, IEnumerable<T>, IEnumerable, INotifyCollectionChanged, ICollectionChangedListener where T: Entity
        {
            private readonly List<T> _addedEntities;
            private readonly EntityCollection<T> _source;
            private readonly object _syncRoot;
            private readonly WeakCollectionChangedListener _weakCollectionChangedLister;
            private NotifyCollectionChangedEventHandler collectionChanged;

            public event NotifyCollectionChangedEventHandler CollectionChanged
            {
                add
                {
                    NotifyCollectionChangedEventHandler handler2;
                    NotifyCollectionChangedEventHandler collectionChanged = this.collectionChanged;
                    do
                    {
                        handler2 = collectionChanged;
                        NotifyCollectionChangedEventHandler handler3 = (NotifyCollectionChangedEventHandler) Delegate.Combine(handler2, value);
                        collectionChanged = Interlocked.CompareExchange<NotifyCollectionChangedEventHandler>(ref this.collectionChanged, handler3, handler2);
                    }
                    while (collectionChanged != handler2);
                }
                remove
                {
                    NotifyCollectionChangedEventHandler handler2;
                    NotifyCollectionChangedEventHandler collectionChanged = this.collectionChanged;
                    do
                    {
                        handler2 = collectionChanged;
                        NotifyCollectionChangedEventHandler handler3 = (NotifyCollectionChangedEventHandler) Delegate.Remove(handler2, value);
                        collectionChanged = Interlocked.CompareExchange<NotifyCollectionChangedEventHandler>(ref this.collectionChanged, handler3, handler2);
                    }
                    while (collectionChanged != handler2);
                }
            }

            internal ListCollectionViewProxy(EntityCollection<T> source)
            {
                this._syncRoot = new object();
                this._addedEntities = new List<T>();
                this._source = source;
                this._weakCollectionChangedLister = WeakCollectionChangedListener.CreateIfNecessary(this._source, this);
            }

            public int Add(object value)
            {
                T item = value as T;
                if (item == null)
                {
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resource.MustBeAnEntity, new object[] { "value" }), "value");
                }
                this._addedEntities.Add(item);
                this.Source.Add(item);
                return this.IndexOf(item);
            }

            public void Clear()
            {
                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, new object[] { "Clear" }));
            }

            public bool Contains(object value)
            {
                return (this.IndexOf(value) >= 0);
            }

            public void CopyTo(Array array, int index)
            {
                this.Source.Entities.CopyTo(array, index);
            }

            public IEnumerator<T> GetEnumerator()
            {
                return this.Source.GetEnumerator();
            }

            public int IndexOf(object value)
            {
                return this.Source.Entities.IndexOf(value);
            }

            public void Insert(int index, object value)
            {
                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, new object[] { "Insert" }));
            }

            private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                NotifyCollectionChangedEventHandler collectionChanged = this.collectionChanged;
                if (collectionChanged != null)
                {
                    collectionChanged(this, e);
                }
            }

            private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                this.OnCollectionChanged(e);
            }

            public void Remove(object value)
            {
                T entity = value as T;
                if (entity != null)
                {
                    this.Source.Remove(entity);
                    if (this._addedEntities.Contains(entity))
                    {
                        this._addedEntities.Remove(entity);
                        this.Source.SourceSet.Remove(entity);
                    }
                }
            }

            public void RemoveAt(int index)
            {
                this.Remove(this[index]);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            void ICollectionChangedListener.OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                this.OnSourceCollectionChanged(sender, e);
            }

            public int Count
            {
                get
                {
                    return this.Source.Count;
                }
            }

            public bool IsFixedSize
            {
                get
                {
                    return false;
                }
            }

            public bool IsReadOnly
            {
                get
                {
                    return false;
                }
            }

            public bool IsSynchronized
            {
                get
                {
                    return false;
                }
            }

            public object this[int index]
            {
                get
                {
                    if ((index >= 0) && (index < this.Source.Count))
                    {
                        return this.Source.Entities[index];
                    }
                    return null;
                }
                set
                {
                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.IsNotSupported, new object[] { "Indexed setting" }));
                }
            }

            private EntityCollection<T> Source
            {
                get
                {
                    return this._source;
                }
            }

            public object SyncRoot
            {
                get
                {
                    return this._syncRoot;
                }
            }
        }
    }
}

